use std::collections::HashMap;

use crate::{
    option::Options,
    r#type::{RequestType, ResponseType},
    response::Response,
};
use anyhow::{anyhow, Result};
use napi_derive_ohos::napi;
use napi_ohos::bindgen_prelude::Buffer;
use reqwest::{ClientBuilder, Method};
use serde_json::Value;

#[napi]
pub struct HttpClient {
    client: Option<reqwest::Client>,
}

#[napi]
impl HttpClient {
    #[napi(constructor)]
    pub fn new() -> Self {
        if let Ok(client) = ClientBuilder::new()
            .danger_accept_invalid_certs(true)
            .danger_accept_invalid_hostnames(true)
            .min_tls_version(reqwest::tls::Version::TLS_1_0)
            .max_tls_version(reqwest::tls::Version::TLS_1_2)
            .build()
        {
            Self {
                client: Some(client),
            }
        } else {
            Self { client: None }
        }
    }

    #[napi]
    pub async fn send_request(&self, option: Options) -> Result<Response> {
        self.send_request_inner(&option).await
    }

    pub async fn send_request_inner(&self, option: &Options) -> Result<Response> {
        if let Some(client) = &self.client {
            let method = match option.request_type {
                RequestType::POST => Method::POST,
                RequestType::PUT => Method::PUT,
                RequestType::DELETE => Method::DELETE,
                _ => Method::GET,
            };
            let mut builder = client.request(method, &option.url);
            if let Some(headers) = &option.headers {
                for (key, value) in headers.iter() {
                    builder = builder.header(key, value);
                }
            }
            if option.request_type == RequestType::POST || option.request_type == RequestType::PUT {
                if let Some(json_data) = &option.json_data {
                    let value: Value = serde_json::from_str(json_data)?;
                    if let Value::Object(map) = value {
                        let hashmap: HashMap<String, Value> = map.into_iter().collect();
                        builder = builder.json(&hashmap);
                    }
                }
            }
            let resp = builder.send().await?;
            let status_code = resp.status().as_u16();
            let mut text = String::default();
            let mut buffer = None;
            match resp.error_for_status() {
                Ok(res) => {
                    if option.response_type == ResponseType::STRING {
                        text = res.text().await?;
                    } else if let Ok(bytes) = res.bytes().await {
                        buffer = Some(Buffer::from(bytes.to_vec()));
                    }
                }
                Err(err) => {
                    text = err.to_string();
                }
            }
            Ok(Response {
                text,
                buffer,
                status_code,
            })
        } else {
            Err(anyhow!("init request failed"))
        }
    }
}
